odhcp6c: implement asynchronous handling for DHCPv6 State
authorNicolas BESNARD <[email protected]>
Tue, 29 Oct 2024 10:27:13 +0000 (10:27 +0000)
committerÁlvaro Fernández Rojas <[email protected]>
Mon, 3 Nov 2025 15:14:49 +0000 (16:14 +0100)
Problem:
    The DHCPv6 states were previously handled synchronously, meaning
    that while waiting for a response from the server, no other actions
    could be performed in odhcp6c.

    The process would block inside a loop until a valid response was
    received, limiting responsiveness and overall system performance.

Solution:
    The new approach involves sending the requests and then
    asynchronously monitoring the DHCPv6 socket using poll(). By reading
    a response only when there is data available on the socket, the
    system remains non-blocking.

Signed-off-by: Nicolas BESNARD <[email protected]>
Signed-off-by: Paul Donald <[email protected]>
Link: https://github.com/openwrt/odhcp6c/pull/106
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
src/dhcpv6.c
src/odhcp6c.c
src/odhcp6c.h

index 410a2f5ffa625b9870c273682355ddb304ab276f..411a3f72bdae436e1531546397ad4d321130b17f 100644 (file)
@@ -81,19 +81,19 @@ static int dhcpv6_commit_advert(void);
 // RFC 3315 - 5.5 Timeout and Delay values
 static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = {
        [DHCPV6_MSG_UNKNOWN] = {false, 1, 120, 0, "<POLL>",
-                       dhcpv6_handle_reconfigure, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1},
+                       dhcpv6_handle_reconfigure, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1, 0},
        [DHCPV6_MSG_SOLICIT] = {true, 1, DHCPV6_SOL_MAX_RT, 0, "SOLICIT",
-                       dhcpv6_handle_advert, dhcpv6_commit_advert, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1},
+                       dhcpv6_handle_advert, dhcpv6_commit_advert, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1, 0},
        [DHCPV6_MSG_REQUEST] = {true, 1, DHCPV6_REQ_MAX_RT, 10, "REQUEST",
-                       dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1},
+                       dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1, 0},
        [DHCPV6_MSG_RENEW] = {false, 10, DHCPV6_REN_MAX_RT, 0, "RENEW",
-                       dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1},
+                       dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1, 0},
        [DHCPV6_MSG_REBIND] = {false, 10, DHCPV6_REB_MAX_RT, 0, "REBIND",
-                       dhcpv6_handle_rebind_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1},
-       [DHCPV6_MSG_RELEASE] = {false, 1, 0, 5, "RELEASE", NULL, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1},
-       [DHCPV6_MSG_DECLINE] = {false, 1, 0, 5, "DECLINE", NULL, NULL, false, 0, 0, 0,{0, 0, 0}, 0, 0, 0, -1},
+                       dhcpv6_handle_rebind_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1, 0},
+       [DHCPV6_MSG_RELEASE] = {false, 1, 0, 5, "RELEASE", NULL, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1, 0},
+       [DHCPV6_MSG_DECLINE] = {false, 1, 0, 5, "DECLINE", NULL, NULL, false, 0, 0, 0,{0, 0, 0}, 0, 0, 0, -1, 0},
        [DHCPV6_MSG_INFO_REQ] = {true, 1, DHCPV6_INF_MAX_RT, 0, "INFOREQ",
-                       dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1},
+                       dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1, 0},
 };
 
 // Sockets
@@ -108,6 +108,10 @@ static bool accept_reconfig = false;
 // Server unicast address
 static struct in6_addr server_addr = IN6ADDR_ANY_INIT;
 
+// Initial state of the dhcpv6
+static enum dhcpv6_state dhcpv6_state = DHCPV6_INIT;
+static int dhcpv6_state_timeout = 0;
+
 // Reconfigure key
 static uint8_t reconf_key[16];
 
@@ -122,6 +126,18 @@ static uint32_t ntohl_unaligned(const uint8_t *data)
        return ntohl(buf);
 }
 
+static void dhcpv6_next_state(void)
+{
+       dhcpv6_state++;
+       dhcpv6_reset_state_timeout();
+}
+
+static void dhcpv6_prev_state(void)
+{
+       dhcpv6_state--;
+       dhcpv6_reset_state_timeout();
+}
+
 static char *dhcpv6_msg_to_str(enum dhcpv6_msg msg)
 {
        switch (msg) {
@@ -214,6 +230,39 @@ static int fd_set_nonblocking(int sockfd)
        return 0;
 }
 
+int dhcpv6_get_socket(void)
+{
+       return sock;
+}
+
+enum dhcpv6_state dhcpv6_get_state(void)
+{
+       return dhcpv6_state;
+}
+
+void dhcpv6_set_state(enum dhcpv6_state state)
+{
+       dhcpv6_state = state;
+       dhcpv6_reset_state_timeout();
+}
+
+int dhcpv6_get_state_timeout(void)
+{
+       return dhcpv6_state_timeout;
+}
+
+void dhcpv6_set_state_timeout(int timeout)
+{
+       if (timeout > 0 && (dhcpv6_state_timeout == 0 || timeout < dhcpv6_state_timeout)) {
+               dhcpv6_state_timeout = timeout;
+       }
+}
+
+void dhcpv6_reset_state_timeout(void)
+{
+       dhcpv6_state_timeout = 0;
+}
+
 int init_dhcpv6(const char *ifname, unsigned int options, int sk_prio, int sol_timeout, unsigned int dscp)
 {
        client_options = options;
@@ -679,156 +728,6 @@ static int64_t dhcpv6_rand_delay(int64_t time)
        return (time * ((int64_t)random % 1000LL)) / 10000LL;
 }
 
-int dhcpv6_request(enum dhcpv6_msg type)
-{
-       uint8_t rc = 0;
-       uint64_t timeout = UINT32_MAX;
-       struct dhcpv6_retx *retx = &dhcpv6_retx[type];
-
-       if (retx->delay) {
-               struct timespec ts = {0, 0};
-               ts.tv_nsec = (dhcpv6_rand_delay((10000 * DHCPV6_REQ_DELAY) / 2) + (1000 * DHCPV6_REQ_DELAY) / 2) * 1000000;
-
-               while (nanosleep(&ts, &ts) < 0 && errno == EINTR);
-       }
-
-       if (type == DHCPV6_MSG_UNKNOWN)
-               timeout = t1;
-       else if (type == DHCPV6_MSG_RENEW)
-               timeout = (t2 > t1) ? t2 - t1 : ((t1 == UINT32_MAX) ? UINT32_MAX : 0);
-       else if (type == DHCPV6_MSG_REBIND)
-               timeout = (t3 > t2) ? t3 - t2 : ((t2 == UINT32_MAX) ? UINT32_MAX : 0);
-
-       if (timeout == 0)
-               return -1;
-
-       syslog(LOG_NOTICE, "Starting %s transaction (timeout %"PRIu64"s, max rc %d)",
-                       retx->name, timeout, retx->max_rc);
-
-       uint64_t start = odhcp6c_get_milli_time(), round_start = start, elapsed;
-
-       // Generate transaction ID
-       uint8_t trid[3] = {0, 0, 0};
-       if (type != DHCPV6_MSG_UNKNOWN)
-               odhcp6c_random(trid, sizeof(trid));
-
-       ssize_t len = -1;
-       int64_t rto = 0;
-
-       do {
-               if (rto == 0) {
-                       int64_t delay = dhcpv6_rand_delay(retx->init_timeo * 1000);
-
-                       // First RT MUST be strictly greater than IRT for solicit messages (RFC3313 17.1.2)
-                       while (type == DHCPV6_MSG_SOLICIT && delay <= 0)
-                               delay = dhcpv6_rand_delay(retx->init_timeo * 1000);
-
-                       rto = (retx->init_timeo * 1000 + delay);
-               } else
-                       rto = (2 * rto + dhcpv6_rand_delay(rto));
-
-               if (retx->max_timeo && (rto >= retx->max_timeo * 1000))
-                       rto = retx->max_timeo * 1000 +
-                               dhcpv6_rand_delay(retx->max_timeo * 1000);
-
-               // Calculate end for this round and elapsed time
-               uint64_t round_end = round_start + rto;
-               elapsed = round_start - start;
-
-               // Don't wait too long if timeout differs from infinite
-               if ((timeout != UINT32_MAX) && (round_end - start > timeout * 1000))
-                       round_end = timeout * 1000 + start;
-
-               // Built and send package
-               switch (type) {
-               case DHCPV6_MSG_UNKNOWN:
-                       break;
-               default:
-                       syslog(LOG_NOTICE, "Send %s message (elapsed %"PRIu64"ms, rc %d)",
-                                       retx->name, elapsed, rc);
-                       // Fall through
-               case DHCPV6_MSG_SOLICIT:
-               case DHCPV6_MSG_INFO_REQ:
-                       dhcpv6_send(type, trid, elapsed / 10);
-                       rc++;
-               }
-
-               // Receive rounds
-               for (; len < 0 && (round_start < round_end);
-                               round_start = odhcp6c_get_milli_time()) {
-                       uint8_t buf[1536];
-                       union {
-                               struct cmsghdr hdr;
-                               uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
-                       } cmsg_buf;
-                       struct iovec iov = {buf, sizeof(buf)};
-                       struct sockaddr_in6 addr;
-                       struct msghdr msg = {.msg_name = &addr, .msg_namelen = sizeof(addr),
-                                       .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsg_buf.buf,
-                                       .msg_controllen = sizeof(cmsg_buf)};
-                       struct in6_pktinfo *pktinfo = NULL;
-                       const struct dhcpv6_header *hdr = (const struct dhcpv6_header *)buf;
-
-                       // Check for pending signal
-                       if (odhcp6c_signal_process())
-                               return -1;
-
-                       // Set timeout for receiving
-                       uint64_t t = round_end - round_start;
-                       struct timeval tv = {t / 1000, (t % 1000) * 1000};
-                       if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
-                                       &tv, sizeof(tv)) < 0)
-                               syslog(LOG_ERR, "setsockopt SO_RCVTIMEO failed (%s)",
-                                               strerror(errno));
-
-                       // Receive cycle
-                       len = recvmsg(sock, &msg, 0);
-                       if (len < 0)
-                               continue;
-
-                       for (struct cmsghdr *ch = CMSG_FIRSTHDR(&msg); ch != NULL;
-                               ch = CMSG_NXTHDR(&msg, ch)) {
-                               if (ch->cmsg_level == SOL_IPV6 &&
-                                       ch->cmsg_type == IPV6_PKTINFO) {
-                                       pktinfo = (struct in6_pktinfo *)CMSG_DATA(ch);
-                                       break;
-                               }
-                       }
-
-                       if (pktinfo == NULL) {
-                               len = -1;
-                               continue;
-                       }
-
-                       if (!dhcpv6_response_is_valid(buf, len, trid,
-                                                       type, &pktinfo->ipi6_addr)) {
-                               len = -1;
-                               continue;
-                       }
-
-                       uint8_t *opt = &buf[4];
-                       uint8_t *opt_end = opt + len - 4;
-
-                       round_start = odhcp6c_get_milli_time();
-                       elapsed = round_start - start;
-                       syslog(LOG_NOTICE, "Got a valid %s after %"PRIu64"ms",
-                              dhcpv6_msg_to_str(hdr->msg_type), elapsed);
-
-                       if (retx->handler_reply)
-                               len = retx->handler_reply(type, rc, opt, opt_end, &addr);
-
-                       if (len > 0 && round_end - round_start > 1000)
-                               round_end = 1000 + round_start;
-               }
-
-               // Allow
-               if (retx->handler_finish)
-                       len = retx->handler_finish();
-       } while (len < 0 && ((timeout == UINT32_MAX) || (elapsed / 1000 < timeout)) &&
-                       (!retx->max_rc || rc < retx->max_rc));
-       return len;
-}
-
 // Message validation checks according to RFC3315 chapter 15
 static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
                const uint8_t transaction[3], enum dhcpv6_msg type,
@@ -933,29 +832,6 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
        return clientid_ok && serverid_ok;
 }
 
-int dhcpv6_poll_reconfigure(void)
-{
-       int ret = dhcpv6_request(DHCPV6_MSG_UNKNOWN);
-
-       switch (ret) {
-       /*
-        * Only RENEW/REBIND/INFORMATION REQUEST
-        * message transmission can be requested
-        * by a RECONFIGURE
-        */
-       case DHCPV6_MSG_RENEW:
-       case DHCPV6_MSG_REBIND:
-       case DHCPV6_MSG_INFO_REQ:
-               ret = dhcpv6_request(ret);
-               break;
-
-       default:
-               break;
-       }
-
-       return ret;
-}
-
 static int dhcpv6_handle_reconfigure(enum dhcpv6_msg orig, const int rc,
                const void *opt, const void *end, _unused const struct sockaddr_in6 *from)
 {
@@ -1702,8 +1578,10 @@ static void dhcpv6_handle_ia_status_code(const enum dhcpv6_msg orig,
                switch (orig) {
                case DHCPV6_MSG_RENEW:
                case DHCPV6_MSG_REBIND:
-                       if ((*ret > 0) && !handled_status_codes[code])
-                               *ret = dhcpv6_request(DHCPV6_MSG_REQUEST);
+                       if ((*ret > 0) && !handled_status_codes[code]) {
+                               dhcpv6_set_state(DHCPV6_REQUEST);
+                               *ret = -1;
+                       }
                        break;
 
                default:
@@ -1780,7 +1658,8 @@ int dhcpv6_promote_server_cand(void)
                dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = cand->sol_max_rt;
                dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_timeo = cand->inf_max_rt;
 
-               return dhcpv6_request(DHCPV6_MSG_SOLICIT);
+               dhcpv6_set_state(DHCPV6_SOLICIT);
+               return -1;
        }
 
        hdr[0] = htons(DHCPV6_OPT_SERVERID);
@@ -1814,17 +1693,29 @@ int dhcpv6_promote_server_cand(void)
 int dhcpv6_send_request(enum dhcpv6_msg type)
 {
        struct dhcpv6_retx *retx = &dhcpv6_retx[type];
+       uint64_t current_milli_time = 0;
+
+       if (retx->delay ) {
+               if (retx->delay_msec == 0) {
+                       retx->delay_msec = (dhcpv6_rand_delay((10000 * DHCPV6_REQ_DELAY) / 2) + (1000 * DHCPV6_REQ_DELAY) / 2);
+                       dhcpv6_set_state_timeout(retx->delay_msec);
+                       retx->delay_msec += odhcp6c_get_milli_time();
+                       return 1;
+               } else {
+                       current_milli_time = odhcp6c_get_milli_time();
+                       if (current_milli_time < retx->delay_msec) {
+                               dhcpv6_set_state_timeout(retx->delay_msec - current_milli_time);
+                               return 1;
+                       }
+                       retx->delay_msec = 0;
+               }
+       }
 
        if (!retx->is_retransmit) {
                retx->is_retransmit = true;
                retx->rc = 0;
                retx->timeout = UINT32_MAX;
-
-               if (retx->delay) {
-                       struct timespec ts = {0, 0};
-                       ts.tv_nsec = (dhcpv6_rand_delay((10000 * DHCPV6_REQ_DELAY) / 2) + (1000 * DHCPV6_REQ_DELAY) / 2) * 1000000;
-                       while (nanosleep(&ts, &ts) < 0 && errno == EINTR);
-               }
+               retx->reply_ret = -1;
 
                if (type == DHCPV6_MSG_UNKNOWN)
                        retx->timeout = t1;
@@ -1873,6 +1764,8 @@ int dhcpv6_send_request(enum dhcpv6_msg type)
        if ((retx->timeout != UINT32_MAX) && (retx->round_end - retx->start > retx->timeout * 1000))
                retx->round_end = retx->timeout * 1000 + retx->start;
 
+       dhcpv6_set_state_timeout(retx->round_end - odhcp6c_get_milli_time());
+
        // Built and send package
        switch (type) {
        case DHCPV6_MSG_UNKNOWN:
@@ -1886,6 +1779,10 @@ int dhcpv6_send_request(enum dhcpv6_msg type)
                dhcpv6_send(type, retx->tr_id, elapsed / 10);
                retx->rc++;
        }
+       
+       if (dhcpv6_get_state() != DHCPV6_EXIT)
+               dhcpv6_next_state();
+
        return 0;
 }
 
@@ -1952,3 +1849,29 @@ int dhcpv6_receive_response(enum dhcpv6_msg type)
 
        return retx->reply_ret;
 }
+
+int dhcpv6_state_processing(enum dhcpv6_msg type)
+{
+       struct dhcpv6_retx *retx = &dhcpv6_retx[type];
+       int ret = retx->reply_ret;
+       retx->round_start = odhcp6c_get_milli_time();
+       uint64_t elapsed = retx->round_start - retx->start;
+
+       if (retx->round_start >= retx->round_end || ret >=0 ) {
+               if (retx->handler_finish)
+                       ret = retx->handler_finish();
+               
+               if (ret < 0 && ((retx->timeout == UINT32_MAX) || (elapsed / 1000 < retx->timeout)) &&
+                       (!retx->max_rc || retx->rc < retx->max_rc)) {
+                               retx->reply_ret = -1;
+                               dhcpv6_prev_state();
+               } else {
+                       retx->is_retransmit = false;
+                       dhcpv6_next_state();
+               }
+       } else {
+               dhcpv6_set_state_timeout(retx->round_end - retx->round_start);
+       }
+
+       return ret;
+}
index f4399013036caa1a76951798819628b37f218785..ee8d96a468fd43dc872b31f225101d2243815d06 100644 (file)
@@ -31,6 +31,7 @@
 
 #include <net/if.h>
 #include <sys/syscall.h>
+#include <poll.h>
 #include <arpa/inet.h>
 #include <linux/if_addr.h>
 
@@ -183,11 +184,13 @@ int main(_unused int argc, char* const argv[])
        int verbosity = 0;
        bool help = false, daemonize = false;
        int logopt = LOG_PID;
-       int c, res;
+       int c;
+       int res = -1;
        unsigned int client_options = DHCPV6_CLIENT_FQDN | DHCPV6_ACCEPT_RECONFIGURE;
        unsigned int ra_options = RA_RDNSS_DEFAULT_LIFETIME;
        unsigned int ra_holdoff_interval = RA_MIN_ADV_INTERVAL;
        unsigned int dscp = 0;
+       bool terminate = false;
 
        while ((c = getopt(argc, argv, "S::DN:V:P:FB:c:i:r:Ru:Ux:s:kK:t:C:m:Lhedp:fav")) != -1) {
                switch (c) {
@@ -470,136 +473,232 @@ int main(_unused int argc, char* const argv[])
                return 4;
        }
 
-    script_call("started", 0, false);
+       struct pollfd fds[2] = {0};
+       int nfds = 0;
 
-       while (!signal_term) { // Main logic
-               odhcp6c_clear_state(STATE_SERVER_ID);
-               odhcp6c_clear_state(STATE_SERVER_ADDR);
-               odhcp6c_clear_state(STATE_IA_NA);
-               odhcp6c_clear_state(STATE_IA_PD);
-               odhcp6c_clear_state(STATE_SNTP_IP);
-               odhcp6c_clear_state(STATE_NTP_IP);
-               odhcp6c_clear_state(STATE_NTP_FQDN);
-               odhcp6c_clear_state(STATE_SIP_IP);
-               odhcp6c_clear_state(STATE_SIP_FQDN);
-               bound = false;
+       int dhcpv6_socket = dhcpv6_get_socket();
+       int mode = DHCPV6_UNKNOWN;
+       enum dhcpv6_msg msg_type = DHCPV6_MSG_UNKNOWN;
 
-               syslog(LOG_NOTICE, "(re)starting transaction on %s", ifname);
-
-               signal_usr1 = signal_usr2 = false;
-               int mode = dhcpv6_set_ia_mode(ia_na_mode, ia_pd_mode, stateful_only_mode);
-               if (mode != DHCPV6_STATELESS)
-                       mode = dhcpv6_request(DHCPV6_MSG_SOLICIT);
+       if (dhcpv6_socket < 0) {
+               syslog(LOG_ERR, "Invalid dhcpv6 file descriptor");
+               return 1;
+       }
+       
+       fds[nfds].fd = dhcpv6_socket;
+       fds[nfds].events = POLLIN;
+       nfds++;
+
+       script_call("started", 0, false);
+
+       while (!terminate) { // Main logic
+               int poll_res;
+               bool signalled = odhcp6c_signal_process(); 
+
+               switch (dhcpv6_get_state()) {
+               case DHCPV6_INIT:
+                       odhcp6c_clear_state(STATE_SERVER_ID);
+                       odhcp6c_clear_state(STATE_SERVER_ADDR);
+                       odhcp6c_clear_state(STATE_IA_NA);
+                       odhcp6c_clear_state(STATE_IA_PD);
+                       odhcp6c_clear_state(STATE_SNTP_IP);
+                       odhcp6c_clear_state(STATE_NTP_IP);
+                       odhcp6c_clear_state(STATE_NTP_FQDN);
+                       odhcp6c_clear_state(STATE_SIP_IP);
+                       odhcp6c_clear_state(STATE_SIP_FQDN);
+                       bound = false;
+
+                       syslog(LOG_NOTICE, "(re)starting transaction on %s", ifname);
+
+                       signal_usr1 = signal_usr2 = false;
+                       dhcpv6_set_state(DHCPV6_SOLICIT);
+                       break;
 
-               odhcp6c_signal_process();
+               case DHCPV6_SOLICIT:
+                       mode = dhcpv6_set_ia_mode(ia_na_mode, ia_pd_mode, stateful_only_mode);
+                       if (mode == DHCPV6_STATELESS) {
+                               dhcpv6_set_state(DHCPV6_REQUEST);
+                               break;
+                       }
+                       
+                       msg_type = DHCPV6_MSG_SOLICIT;
+                       dhcpv6_send_request(msg_type);          
+                       break;
 
-               if (mode < 0)
-                       continue;
+               case DHCPV6_ADVERT:
+                       if (res > 0) {
+                               mode = DHCPV6_STATEFUL;
+                               dhcpv6_set_state(DHCPV6_REQUEST);
+                       } else {
+                               mode = DHCPV6_UNKNOWN;
+                               dhcpv6_set_state(DHCPV6_INIT);
+                       }
+                       break;
 
-               do {
-                       res = dhcpv6_request(mode == DHCPV6_STATELESS ?
-                                       DHCPV6_MSG_INFO_REQ : DHCPV6_MSG_REQUEST);
-                       bool signalled = odhcp6c_signal_process();
+               case DHCPV6_REQUEST:
+                       msg_type = (mode == DHCPV6_STATELESS) ? DHCPV6_MSG_INFO_REQ : DHCPV6_MSG_REQUEST;
+                       dhcpv6_send_request(msg_type);
+                       break;
 
-                       if (res > 0)
+               case DHCPV6_REPLY:
+                       if ((res > 0) && mode != DHCPV6_UNKNOWN) {
+                               dhcpv6_set_state(DHCPV6_BOUND);
                                break;
-                       else if (signalled) {
-                               mode = -1;
+                       }
+
+                       if ((res < 0) && signalled) {
+                               mode = DHCPV6_UNKNOWN;
+                               dhcpv6_set_state(DHCPV6_INIT);
                                break;
                        }
 
                        mode = dhcpv6_promote_server_cand();
-               } while (mode > DHCPV6_UNKNOWN);
-
-               if (mode < 0)
-                       continue;
-
-               switch (mode) {
-               case DHCPV6_STATELESS:
-                       bound = true;
-                       syslog(LOG_NOTICE, "entering stateless-mode on %s", ifname);
-
-                       while (!signal_usr2 && !signal_term) {
-                               signal_usr1 = false;
-                               script_call("informed", ra ? script_accu_delay : script_sync_delay, true);
-
-                               res = dhcpv6_poll_reconfigure();
-                               odhcp6c_signal_process();
-
-                               if (res > 0)
-                                       continue;
+                       dhcpv6_set_state(mode > DHCPV6_UNKNOWN ? DHCPV6_REQUEST : DHCPV6_INIT);
+                       break;
 
-                               if (signal_usr1) {
-                                       signal_usr1 = false; // Acknowledged
-                                       continue;
+               case DHCPV6_BOUND:
+                       if (!bound) {
+                               bound = true;
+                               if (mode == DHCPV6_STATELESS) {
+                                       syslog(LOG_NOTICE, "entering stateless-mode on %s", ifname);
+                                       signal_usr1 = false;
+                                       script_call("informed", script_sync_delay, true);
+                               } else {
+                                       script_call("bound", script_sync_delay, true);
+                                       syslog(LOG_NOTICE, "entering stateful-mode on %s", ifname);
                                }
+                       }
 
-                               if (signal_usr2 || signal_term)
-                                       break;
-
-                               res = dhcpv6_request(DHCPV6_MSG_INFO_REQ);
-                               odhcp6c_signal_process();
-
-                               if (signal_usr1)
-                                       continue;
-                               else if (res < 0)
-                                       break;
+                       msg_type = DHCPV6_MSG_UNKNOWN;
+                       dhcpv6_send_request(msg_type);
+                       break;
+               
+               case DHCPV6_BOUND_REPLY:
+                       if (res == DHCPV6_MSG_RENEW || res == DHCPV6_MSG_REBIND ||
+                               res == DHCPV6_MSG_INFO_REQ) {
+                               msg_type = res;
+                               dhcpv6_set_state(DHCPV6_RECONF);                        
+                       } else {
+                               dhcpv6_set_state(DHCPV6_RECONF_REPLY);
                        }
                        break;
 
-               case DHCPV6_STATEFUL:
-                       bound = true;
-                       script_call("bound", ra ? script_accu_delay : script_sync_delay, true);
-                       syslog(LOG_NOTICE, "entering stateful-mode on %s", ifname);
-
-                       while (!signal_usr2 && !signal_term) {
-                               // Renew Cycle
-                               // Wait for T1 to expire or until we get a reconfigure
-                               res = dhcpv6_poll_reconfigure();
-                               odhcp6c_signal_process();
-                               if (res > 0) {
-                                       script_call("updated", 0, false);
-                                       continue;
-                               }
+               case DHCPV6_RECONF:     
+                       dhcpv6_send_request(msg_type);
+                       break;
 
-                               // Handle signal, if necessary
-                               if (signal_usr1)
-                                       signal_usr1 = false; // Acknowledged
+               case DHCPV6_RECONF_REPLY:
+                       if (res > 0) {
+                               dhcpv6_set_state(DHCPV6_BOUND);
+                               if (mode == DHCPV6_STATEFUL)
+                                       script_call("updated", 0, false);
+                       } else {
+                               dhcpv6_set_state(mode == DHCPV6_STATELESS ? DHCPV6_INFO : DHCPV6_RENEW);
+                       }
+                       break;
+               
+               case DHCPV6_RENEW:
+                       msg_type = DHCPV6_MSG_RENEW;
+                       dhcpv6_send_request(msg_type);
+                       break;
+               
+               case DHCPV6_RENEW_REPLY:
+                       if (res > 0 ) {
+                               script_call("updated", 0, false);
+                               dhcpv6_set_state(DHCPV6_BOUND);
+                       } else {
+                               dhcpv6_set_state(DHCPV6_REBIND);
+                       }
+                       break;
 
-                               if (signal_usr2 || signal_term)
-                                       break; // Other signal type
+               case DHCPV6_REBIND:
+                       odhcp6c_clear_state(STATE_SERVER_ID); // Remove binding
+                       odhcp6c_clear_state(STATE_SERVER_ADDR);
 
-                               // Send renew as T1 expired
-                               res = dhcpv6_request(DHCPV6_MSG_RENEW);
-                               odhcp6c_signal_process();
+                       size_t ia_pd_len_r, ia_na_len_r;
+                       odhcp6c_get_state(STATE_IA_PD, &ia_pd_len_r);
+                       odhcp6c_get_state(STATE_IA_NA, &ia_na_len_r);
 
-                               if (res > 0) { // Renew was succesfull
-                                       // Publish updates
-                                       script_call("updated", 0, false);
-                                       continue; // Renew was successful
-                               }
+                       if (ia_pd_len_r == 0 && ia_na_len_r == 0) {
+                               dhcpv6_set_state(DHCPV6_EXIT);
+                               break;
+                       }
 
-                               odhcp6c_clear_state(STATE_SERVER_ID); // Remove binding
-                               odhcp6c_clear_state(STATE_SERVER_ADDR);
+                       // If we have IAs, try rebind otherwise restart
+                       msg_type = DHCPV6_MSG_REBIND;
+                       dhcpv6_send_request(msg_type);
+                       break;
+               
+               case DHCPV6_REBIND_REPLY:
+                       if (res < 0) {
+                               dhcpv6_set_state(DHCPV6_EXIT);
+                       } else {
+                               script_call("rebound", 0, true);
+                               dhcpv6_set_state(DHCPV6_BOUND);
+                       }
+                       break;
 
-                               // Remove any state invalidated by RENEW reply
-                               odhcp6c_expire(true);
+               case DHCPV6_INFO:
+                       msg_type = DHCPV6_MSG_INFO_REQ;
+                       dhcpv6_send_request(msg_type);
+                       break;
+               
+               case DHCPV6_INFO_REPLY:
+                       dhcpv6_set_state(res < 0 ? DHCPV6_EXIT : DHCPV6_BOUND);
+                       break;
 
-                               size_t ia_pd_len, ia_na_len;
-                               odhcp6c_get_state(STATE_IA_PD, &ia_pd_len);
-                               odhcp6c_get_state(STATE_IA_NA, &ia_na_len);
+               case DHCPV6_SOLICIT_PROCESSING:
+               case DHCPV6_REQUEST_PROCESSING:
+                       res = dhcpv6_state_processing(msg_type);
 
-                               if (ia_pd_len == 0 && ia_na_len == 0)
-                                       break;
+                       if (signal_usr2 || signal_term)
+                               dhcpv6_set_state(DHCPV6_EXIT);
+                       break;
 
-                               // If we have IAs, try rebind otherwise restart
-                               res = dhcpv6_request(DHCPV6_MSG_REBIND);
-                               odhcp6c_signal_process();
+               case DHCPV6_BOUND_PROCESSING:
+               case DHCPV6_RECONF_PROCESSING:
+               case DHCPV6_REBIND_PROCESSING:
+                       res = dhcpv6_state_processing(msg_type);
 
-                               if (res > 0)
-                                       script_call("rebound", 0, true);
-                               else
-                                       break;
+                       if (signal_usr1)
+                               dhcpv6_set_state(mode == DHCPV6_STATELESS ? DHCPV6_INFO : DHCPV6_RENEW);
+                       if (signal_usr2 || signal_term)
+                               dhcpv6_set_state(DHCPV6_EXIT);
+                       break;
+               
+               case DHCPV6_RENEW_PROCESSING:
+               case DHCPV6_INFO_PROCESSING:
+                       res = dhcpv6_state_processing(msg_type);
+
+                       if (signal_usr1)
+                               signal_usr1 = false;    // Acknowledged
+                       if (signal_usr2 || signal_term)
+                               dhcpv6_set_state(DHCPV6_EXIT);
+                       break;
+               
+               case DHCPV6_EXIT:
+                       odhcp6c_expire(false);
+
+                       size_t ia_pd_len, ia_na_len, server_id_len;
+                       odhcp6c_get_state(STATE_IA_PD, &ia_pd_len);
+                       odhcp6c_get_state(STATE_IA_NA, &ia_na_len);
+                       odhcp6c_get_state(STATE_SERVER_ID, &server_id_len);
+
+                       // Add all prefixes to lost prefixes
+                       bound = false;
+                       script_call("unbound", 0, true);
+
+                       if (server_id_len > 0 && (ia_pd_len > 0 || ia_na_len > 0) && release)
+                               dhcpv6_send_request(DHCPV6_MSG_RELEASE);
+
+                       odhcp6c_clear_state(STATE_IA_NA);
+                       odhcp6c_clear_state(STATE_IA_PD);
+
+                       if (!signal_usr2) {
+                               terminate = true;
+                       } else {
+                               signal_usr2 = false;
+                               dhcpv6_set_state(DHCPV6_INIT);
                        }
                        break;
 
@@ -607,24 +706,16 @@ int main(_unused int argc, char* const argv[])
                        break;
                }
 
-               odhcp6c_expire(false);
-
-               size_t ia_pd_len, ia_na_len, server_id_len;
-               odhcp6c_get_state(STATE_IA_PD, &ia_pd_len);
-               odhcp6c_get_state(STATE_IA_NA, &ia_na_len);
-               odhcp6c_get_state(STATE_SERVER_ID, &server_id_len);
-
-               // Add all prefixes to lost prefixes
-               bound = false;
-               script_call("unbound", 0, true);
+               poll_res = poll(fds, nfds, dhcpv6_get_state_timeout());
+               dhcpv6_reset_state_timeout();
+               if (poll_res == -1 && (errno == EINTR || errno == EAGAIN)) {
+                       continue;
+               }
 
-               if (server_id_len > 0 && (ia_pd_len > 0 || ia_na_len > 0) && release)
-                       dhcpv6_request(DHCPV6_MSG_RELEASE);
+               if (fds[0].revents & POLLIN)
+                       dhcpv6_receive_response(msg_type);
 
-               odhcp6c_clear_state(STATE_IA_NA);
-               odhcp6c_clear_state(STATE_IA_PD);
        }
-
        script_call("stopped", 0, true);
 
        return 0;
index 6a2fc0492fd25502120902f18587b44993cc1fad..aba01b0b0f0b6bfa5b5a05db3b34594d64b25b84 100644 (file)
@@ -152,6 +152,32 @@ enum dhcpv6_msg {
        _DHCPV6_MSG_MAX
 };
 
+enum dhcpv6_state {
+       DHCPV6_INIT,
+       DHCPV6_SOLICIT,
+       DHCPV6_SOLICIT_PROCESSING,
+       DHCPV6_ADVERT,
+       DHCPV6_REQUEST,
+       DHCPV6_REQUEST_PROCESSING,
+       DHCPV6_REPLY,           
+       DHCPV6_BOUND,
+       DHCPV6_BOUND_PROCESSING,
+       DHCPV6_BOUND_REPLY,
+       DHCPV6_RECONF, 
+       DHCPV6_RECONF_PROCESSING,
+       DHCPV6_RECONF_REPLY, 
+       DHCPV6_RENEW, 
+       DHCPV6_RENEW_PROCESSING,
+       DHCPV6_RENEW_REPLY,
+       DHCPV6_REBIND,
+       DHCPV6_REBIND_PROCESSING,
+       DHCPV6_REBIND_REPLY,
+       DHCPV6_INFO,
+       DHCPV6_INFO_PROCESSING,
+       DHCPV6_INFO_REPLY,
+       DHCPV6_EXIT
+};
+
 enum dhcpv6_status {
        DHCPV6_Success = 0,
        DHCPV6_UnspecFail = 1,
@@ -191,6 +217,7 @@ struct dhcpv6_retx {
        uint64_t round_start;
        uint64_t round_end;
        int reply_ret;
+       uint64_t delay_msec;
 };
 
 // DHCPv6 Protocol Headers
@@ -404,11 +431,16 @@ struct odhcp6c_opt {
 
 int init_dhcpv6(const char *ifname, unsigned int client_options, int sk_prio, int sol_timeout, unsigned int dscp);
 int dhcpv6_set_ia_mode(enum odhcp6c_ia_mode na, enum odhcp6c_ia_mode pd, bool stateful_only);
-int dhcpv6_request(enum dhcpv6_msg type);
-int dhcpv6_poll_reconfigure(void);
 int dhcpv6_promote_server_cand(void);
 int dhcpv6_send_request(enum dhcpv6_msg type);
 int dhcpv6_receive_response(enum dhcpv6_msg type);
+enum dhcpv6_state dhcpv6_get_state(void);
+void dhcpv6_set_state(enum dhcpv6_state state);
+int dhcpv6_get_socket(void);
+int dhcpv6_state_processing(enum dhcpv6_msg type);
+int dhcpv6_get_state_timeout(void);
+void dhcpv6_set_state_timeout(int timeout);
+void dhcpv6_reset_state_timeout(void);
 
 int init_rtnetlink(void);
 int set_rtnetlink_addr(int ifindex, const struct in6_addr *addr,